/*
 * Copyright 2007 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.zxing.client.decoder;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import android.os.AsyncTask;

import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;

import com.google.zxing.client.android.ThreadProperty;
import com.google.zxing.common.CharacterSetECI;
import com.google.zxing.common.StringUtils;

/**
 * <p>
 * QR Codes can encode text as bits in one of several modes, and can use multiple modes in one QR Code. This class decodes the bits back into text.
 * </p>
 * 
 * <p>
 * See ISO 18004:2006, 6.4.3 - 6.4.7
 * </p>
 * 
 * @author Sean Owen
 */
final class DecodedBitStreamParser extends Activity {
	private int threadFinishedCounter;

	private int average = 1;
	/**
	 * See ISO 18004:2006, 6.4.4 Table 5
	 */
	private static final char[] ALPHANUMERIC_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
			'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':' };

	DecoderResult decode(byte[] bytes, Version version, ErrorCorrectionLevel ecLevel, Map<DecodeHintType, ?> hints) throws FormatException {
		BitSource bits = new BitSource(bytes);
		StringBuilder result = null;
		List<byte[]> byteSegments = new ArrayList<byte[]>(1);
		Mode mode;
		do {
			if (bits.available() < 4) {
				mode = Mode.TERMINATOR;
			} else {
				try {
					// mode is encoded by 4 bits
					mode = Mode.forBits(bits.readBits(4));
				} catch (IllegalArgumentException iae) {
					throw FormatException.getFormatInstance();
				}
			}
			if (mode != Mode.TERMINATOR) {
				if (mode == Mode.NUMERIC) {
					bits.readBits(mode.getCharacterCountBits(version));
					bits.setOriginalBitOffset(bits.getBitOffset());
					bits.setOriginalByteOffset(bits.getByteOffset());
					// ////////////// IMPLEMENTACION OPTIMIZADA //////////////////////
					// /ADMITO SOLO 1 THREAD 1000 ITERACIONES
					result = new StringBuilder();
					ThreadProperty.getInstance().setTime(Long.MAX_VALUE);
					for (int j = 0; j < 1000; j++) { // Repeticiones para promediar y medir
						
						result = result.delete(0, 153); // limpio antes de comenzar a usar
						bits.initBitSource();//Inicializo los arrays.
						
						long nanoIni = System.nanoTime();
						new DecodeTask(bits, result, 153).execute();
						while (threadFinishedCounter < 1) {
						}
						long nanoFin = System.nanoTime();
						ThreadProperty.getInstance().timeMin(nanoFin - nanoIni);

						threadFinishedCounter = 0;
					}
				} else {
					throw FormatException.getFormatInstance();
				}
			}
		} while (mode != Mode.TERMINATOR);

		return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, ecLevel == null ? null : ecLevel.toString());
	}

	private static char toAlphaNumericChar(int value) throws FormatException {
		if (value >= ALPHANUMERIC_CHARS.length) {
			throw FormatException.getFormatInstance();
		}
		return ALPHANUMERIC_CHARS[value];
	}

	class DecodeTask extends AsyncTask<Void, Void, Void> {

		private BitSource bits;
		private StringBuilder result;
		private int count;

		public DecodeTask(BitSource bits_class, StringBuilder result_class, int count_class) {
			this.bits = bits_class;
			this.result = result_class;
			this.count = count_class;
		}

		protected Void doInBackground(Void... params) {
			ThreeDigitsNumber threeDigitsNumber;
			try {
				while (count >= 3) {
					if (bits.available() < 10) {
						throw FormatException.getFormatInstance();
					}
					int threeDigitsBits = bits.readBits(10);
					if (threeDigitsBits >= 1000) {
						throw FormatException.getFormatInstance();
					}
					threeDigitsNumber = new ThreeDigitsNumber(toAlphaNumericChar(threeDigitsBits / 100), toAlphaNumericChar((threeDigitsBits / 10) % 10), toAlphaNumericChar(threeDigitsBits % 10));
					synchronized (result) {
						result.append(threeDigitsNumber.getDigit());
					}
					count -= 3;
				}
			} catch (FormatException e) {
				e.printStackTrace();
			}
			synchronized (this) {
				threadFinishedCounter++;
			}

			return null;
		}

		protected void setCount(int count) {
			this.count = count;
		}
	}
}
